package cn.itsource.basic.base.service.impl;

import cn.itsource.basic.base.service.ILoginService;
import cn.itsource.basic.exception.CustomException;
import cn.itsource.basic.jwt.JwtUtils;
import cn.itsource.basic.jwt.RsaUtils;
import cn.itsource.basic.jwt.UserInfo;
import cn.itsource.basic.utils.*;
import cn.itsource.org.domain.Logininfo;
import cn.itsource.org.domain.User;
import cn.itsource.org.domain.Wxuser;
import cn.itsource.org.domain.dto.LoginDto;
import cn.itsource.org.domain.dto.SmsCodeParam;
import cn.itsource.org.domain.dto.WxParamDto;
import cn.itsource.org.mapper.LogininfoMapper;
import cn.itsource.org.mapper.UserMapper;
import cn.itsource.org.mapper.WxuserMapper;
import cn.itsource.system.domain.Menu;
import cn.itsource.system.mapper.MenuMapper;
import cn.itsource.system.mapper.PermissionMapper;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.FileCopyUtils;
import sun.net.www.http.HttpClient;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class LoginServiceImpl implements ILoginService {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private LogininfoMapper logininfoMapper;

    @Autowired
    private WxuserMapper wxuserMapper;

    @Autowired
    private PermissionMapper permissionMapper;

    @Autowired
    private MenuMapper menuMapper;

    @Value("${jwt.rsa.pri}")// SPEL表达式读取配置文件中的值
    private String jwtRsaPrivate;

    @Override
    public String verifyCodeImage(String key) {
        //空值校验key
        if(StringUtils.isEmpty(key)){
            throw new CustomException("数据异常");
        }
        //生成随机字符 生成4位数的验证码
        String randomString = StrUtils.getComplexRandomString(4);
        //redis保存生成随机字符
        redisTemplate.opsForValue().set(key,randomString,5, TimeUnit.MINUTES);
        //生成验证码图片base64加密后的字符串  110:是图片宽度 20  是高度
        return VerifyCodeUtils.VerifyCode(110,50,randomString);
    }

    @Override
    public void sendSmsCode(SmsCodeParam param) {
        //判断非空
        if(StringUtils.isEmpty(param.getPhone()) || StringUtils.isEmpty(param.getImageCode())
        || StringUtils.isEmpty(param.getImageCodeKey())){
            throw new CustomException("数据异常");
        }
        //判断用户名是否注册
        if (userMapper.loadByUserName(param.getPhone()) != null ){
            throw new CustomException("用户以注册");
        }
        //通过图形验证码判断key是否存在  o 就是验证码
        Object o = redisTemplate.opsForValue().get(param.getImageCodeKey());
        if (o == null){
            //如果不存在，返回自定义异常（图形验证码过期）
            throw new CustomException("验证码已过期");
        }
        //判断前端传递的图形验证码和redis中获取的是否一致
        if(!o.toString().equalsIgnoreCase(param.getImageCode())){
            //如果不一致，则返回自定义异常
            throw new CustomException("验证码输入错误");
        }
        //拼接短信验证码rediskey（register:手机号），从Redis中获取数据
        String smsKey = "register:"+param.getPhone();
        Object o1 = redisTemplate.opsForValue().get(smsKey);
        String randomString = "";
        //获取到了，说明不是第一次发送，从value（9527:2164654941654641）中截取时间戳，判断当前时间和上次发送时间是否超过一分钟
        if (o1 != null){
            //以:进行分隔成数组
            String[] split = o1.toString().split(":");
            //没超过发短信时间 返回自定义异常  毫秒
            if (System.currentTimeMillis() - Long.valueOf(split[1]) < 6000){
                throw new CustomException("请不要重复发送短信");
            }else {
                //超过1分钟，重新发送短信并且重新更新3分钟有效时间
                redisTemplate.opsForValue().set(smsKey,split[0]+":"+System.currentTimeMillis(),3,TimeUnit.MINUTES);
                //重新发送短信到当前注册手机号
                //SmsUtil.sendSms(param.getPhone(),"验证码"+split[0]);
                System.out.println(param.getPhone()+">>>>>>>"+"验证码:"+split[0]);
            }
        }else {
            //第一次收取短信
            randomString = StrUtils.getComplexRandomString(4);
            //生成3分钟使用时长的验证码
            redisTemplate.opsForValue().set(smsKey,randomString+":"+System.currentTimeMillis(),3,TimeUnit.MINUTES);
            //重新发送短信到当前注册手机号
            //SmsUtil.sendSms(param.getPhone(),"验证码:"+randomString);
            System.out.println(param.getPhone()+">>>>>>>"+"验证码:"+randomString);
        }
        //超过了就以此value中的code作为短信验证码再次发送，并且刷新redis中数据的过期时间
        //没超过，返回自定义异常
        //获取不到，直接发送短信验证码，存储到redis（注意key和value需要业务键和时间戳），设置3分钟过期时间

    }

    @Override
    public Object adminlogin(LoginDto dto) {
        //空参数校验
        if(StringUtils.isEmpty(dto.getUsername()) || StringUtils.isEmpty(dto.getPassword())){
            throw new CustomException("数据异常");
        }
        //查询logininfo表
        Logininfo logininfo = logininfoMapper.queryByUsernameAndType(dto.getUsername(), dto.getType());
        //判断是否有该用户
        if (logininfo == null){
            throw new CustomException("用户不存在，请注册");
        }
        //使用MD5和盐值生成加密的密码  salt是盐值
        String md5password = MD5Utils.encrypByMd5(dto.getPassword() + logininfo.getSalt());
        //如果有判断密码是否正确
        if(!md5password.equals(logininfo.getPassword())){
            throw new CustomException("密码输入错误");
        }
        //生成key  UUID
       /* String token = UUID.randomUUID().toString();
        //如果正确存redis，返回token，用户信息
        redisTemplate.opsForValue().set(token,logininfo,30,TimeUnit.MINUTES);
        //设置密码为null
        logininfo.setPassword(null);
        //设置盐值为null
        logininfo.setSalt(null);
        //页面展示的是键值对集合  所以用map传出去
        Map<String, Object> map = new HashMap<>();
        map.put("token",token);
        map.put("logininfo",logininfo);*/
        return loginSuccessHandler(logininfo);
    }

    @Override
    public AjaxResult wechat(Map<String, String> map) {
        //获取code
        String code = map.get("code");
        //非空参数检验
        if (StringUtils.isEmpty(code)){
            throw new CustomException("微信无响应");
        }
        //拼接请求获取token、openid链接
        String url = WxConstants.GET_ACK_URL;
        //替换自带的参数，换成前端发送进来的参数
        String newUrl = url.replace("APPID", WxConstants.APPID)
                .replace("SECRET", WxConstants.SECRET)
                .replace("CODE", code);
        //发送请求，获取响应值
        String s = HttpClientUtils.httpGet(newUrl);
        //把String结果转换成json对象
        JSONObject jsonObject = JSONObject.parseObject(s);
        //获取JSON中对象的值
        String accessToken = jsonObject.getString("access_token");
        String openId = jsonObject.getString("openid");
        //获取openId去查看用户是否存在
        Wxuser wxuser = wxuserMapper.queryByOpenId(openId);
        //查询该用户是否已经存在
        if (wxuser == null){
            //不存在就返回到注册页面，带上参数去请求用户基本信息
            String finalUrl = "?access_token="+accessToken+"&openid="+openId;
            return AjaxResult.me().setSuccess(false).setResultObj(finalUrl);
        }
        Logininfo logininfo = logininfoMapper.queryByUserId(wxuser.getUserId());

        //生成uuid
       /* String token = UUID.randomUUID().toString();
        //密码存入redis  并重新设置刷新时间
        redisTemplate.opsForValue().set(token,logininfo,30,TimeUnit.MINUTES);
        //在把密码和盐值设置为null
        logininfo.setPassword(null);
        logininfo.setSalt(null);
        //浏览器页面展示需要键值对结构  使用创建一个map
        Map<String, Object> resultMap = new HashMap<>();
        //最后通过map把需要的键和值全传出去
        resultMap.put("token",token);
        resultMap.put("logininfo",logininfo);*/
        return AjaxResult.me().setResultObj(loginSuccessHandler(logininfo));
    }

    @Override
    @Transactional
    public AjaxResult wechbinderat(WxParamDto paramDto) {
        //空值校验
        if (StringUtils.isEmpty(paramDto.getPhone()) || StringUtils.isEmpty(paramDto.getPassword())
        || StringUtils.isEmpty(paramDto.getAccessToken()) || StringUtils.isEmpty(paramDto.getOpenId())){
            throw new CustomException("数据异常");
        }
        //判断用户是否已经存在
        Logininfo logininfo = logininfoMapper.queryByUsernameAndType(paramDto.getPhone(), 1);
        //保存user和logininfo
        //生成盐值
        String salt = StrUtils.getComplexRandomString(32);
        //生成MD5密码
        String md5password = MD5Utils.encrypByMd5(paramDto.getPassword() + salt);
        User user = dto2User(paramDto.getPhone(), salt, md5password);
        Logininfo newLogininfo = user2Logininfo(user);
        logininfoMapper.insert(newLogininfo);
        user.setLogininfoId(newLogininfo.getId());
        userMapper.insert(user);
        //通过请求获取微信用户基本新来来保存wxuser
        String url = WxConstants.GET_USER_URL;
        String resultUrl = url.replace("ACCESS_TOKEN", paramDto.getAccessToken())
                .replace("OPENID", paramDto.getOpenId());
        String s = HttpClientUtils.httpGet(resultUrl);
        //转换为json对象
        JSONObject jo = JSONObject.parseObject(s);
        //给下面的方法赋值
        Wxuser wxuser = jo2WxUser(jo);
        wxuser.setOpenid(paramDto.getOpenId());
        wxuser.setUserId(user.getId());
        wxuserMapper.insert(wxuser);
        //密码正确，生成uuid  作为key  UUID.randomUUID().toString()
        /*String token = UUID.randomUUID().toString();
        //密码存入redis  并重新设置刷新时间    redisTemplate.opsForValue().set();
        redisTemplate.opsForValue().set(token,newLogininfo,30,TimeUnit.MINUTES);
        //在把密码和盐值设置为null
        newLogininfo.setPassword(null);
        newLogininfo.setSalt(null);
        //浏览器页面展示需要键值对结构  使用创建一个map
        Map<String, Object> resultMap = new HashMap<>();
        //最后通过map把需要的键和值全传出去
        resultMap.put("token",token);
        resultMap.put("logininfo",newLogininfo);*/
        return AjaxResult.me().setResultObj(loginSuccessHandler(logininfo));
    }

    public Wxuser jo2WxUser(JSONObject jo){
        Wxuser wxuser = new Wxuser();
        wxuser.setAddress(jo.getString("city"));
        wxuser.setHeadimgurl(jo.getString("headimgurl"));
        wxuser.setNickname(jo.getString("nickname"));
        wxuser.setSex(jo.getInteger("sex"));
        wxuser.setUnionid(jo.getString("unionid"));
        return wxuser;
    }

    private Logininfo user2Logininfo(User newUser) {
        Logininfo logininfo = new Logininfo();
        BeanUtils.copyProperties(newUser,logininfo);
        logininfo.setDisable(1);
        logininfo.setType(1);
        return logininfo;
    }

    private User dto2User(String phone,String salt,String md5Password) {
        User user = new User();
        user.setUsername(phone);
        user.setPhone(phone);
        user.setSalt(salt);
        user.setPassword(md5Password);
        user.setState(1);
        user.setCreatetime(new Date());
        return user;
    }

    public Map<String, Object> loginSuccessHandler(Logininfo logininfo) {
        //创建userinfo
        UserInfo userInfo = new UserInfo();
        //最后返回的是一个map
        Map<String, Object> map = new HashMap<>();
        userInfo.setLogininfo(logininfo);
        //判断当前用户是主站还是后端管理系统
        if (logininfo.getType() == 1){
            //单纯返回个人信息\
            map.put("logininfo",logininfo);
        }else {
            //如果是后端，返回个人信息+权限信息
            List<String> sns = permissionMapper.querySnByLoginInfoId(logininfo.getId());
            List<Menu> menus = menuMapper.queryMenuByLoginInfoId(logininfo.getId());
            userInfo.setPermissions(sns);
            userInfo.setMenus(menus);
            try {
                //获取私钥
                PrivateKey privateKey = RsaUtils
                        .getPrivateKey(FileCopyUtils
                                .copyToByteArray(this.getClass().getClassLoader().getResourceAsStream(jwtRsaPrivate)));
                //加密得到token
                //设置过期时间
                String token = JwtUtils.generateTokenExpireInMinutes(userInfo, privateKey, 30);
                //把密码和盐值设置为null
                logininfo.setPassword(null);
                logininfo.setSalt(null);
                map.put("logininfo",logininfo);
                map.put("token",token);
                map.put("permissions",sns);
                map.put("menus",menus);
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            } catch (InvalidKeySpecException e) {
                throw new RuntimeException(e);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return map;
    }
}
